package com.ys.filelib.filedialog.loader;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageDecoder;
import android.graphics.Matrix;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.util.LruCache;
import android.view.View;
import android.widget.ImageView;

import androidx.fragment.app.Fragment;


import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;

public class FileLoader {

    private static final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
    private static final int cacheSize = maxMemory / 8;
    private static final LruCache<String, Bitmap> sCacheMap = new LruCache<>(cacheSize);
    private static final LruCache<String, Drawable> sGifCacheMap = new LruCache<>(cacheSize / 2);
    private static final Map<String, Disposable> sCacheDispose = new HashMap<>();

    public static RequestBuilder with(Context context) {
        return new RequestBuilder(context);
    }

    public static RequestBuilder with(View view) {
        return with(view.getContext());
    }

    public static RequestBuilder with(Activity activity) {
        return with(activity.getApplication());
    }

    public static RequestBuilder with(Fragment fragment) {
        return with(fragment.getContext());
    }

    public static RequestBuilder with(android.app.Fragment fragment) {
        return with(fragment.getView());
    }

    private static int[] getWidthHeight(String path) {
        int[] dest = new int[2];
        if (path == null) {
            return dest;
        }
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(path, options);
        if (options.outWidth == 0 || options.outHeight == 0) {
            return dest;
        }
        dest[0] = options.outWidth;
        dest[1] = options.outHeight;
        return dest;
    }

    /**
     *
     * @param path 文件路径
     * @param maxPixel 控制bitmap的最大像素值（width*height）
     * @return
     */
    private static Bitmap decodeFile(String path, int maxPixel) {
        if (path == null) {
            return null;
        }
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(path, options);
        if (options.outWidth == 0 || options.outHeight == 0) {
            return null;
        }
        long width = options.outWidth;
        long height = options.outHeight;
        if (width * height <= maxPixel) {
            return BitmapFactory.decodeFile(path);
        }
        options.inSampleSize = 2;
        while ((int)(width / options.inSampleSize * height / options.inSampleSize) > maxPixel) {
            options.inSampleSize++;
        }
        options.inSampleSize--;
        options.inJustDecodeBounds = false;
        Bitmap source = BitmapFactory.decodeFile(path, options);
        width = source.getWidth();
        height = source.getHeight();
        float scale = 1f;
        while ((int) (width * scale * height * scale) > maxPixel) {
            scale -= 0.1f;
        }
        //System.out.println("aaaaaaaaaaaaa: " + source.getWidth() + "  " + source.getHeight() + " " + options.inSampleSize + " " + scale);
        Bitmap destImage = getScaleBitmap(source, scale, scale);
        if (source != destImage) {
            source.recycle();
        }
        return destImage;
    }

    /**
     * Matrix缩放
     * @param bitmap
     * @param scaleX 0~1
     * @param scaleY 0~1
     */
    public static Bitmap getScaleBitmap(Bitmap bitmap, float scaleX, float scaleY) {
        Matrix matrix = new Matrix();
        matrix.postScale(scaleX, scaleY);
        return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
    }

    public static class RequestBuilder {
        private Context context;
        private Object loadUrl;
        private int width;
        private int height;
        private int placeHolder;
        private int error;
        private boolean asBitmap;
        private ImageView targetView;
        private Disposable disposable;

        private RequestBuilder(Context context) {
            this.context = context;
        }

        public RequestBuilder load(String path) {
            return load(new File(path));
        }

        public RequestBuilder load(File file) {
            this.loadUrl = file;
            return this;
        }

        public RequestBuilder asBitmap() {
            this.asBitmap = true;
            return this;
        }

        public RequestBuilder placeHolder(int holder) {
            this.placeHolder = holder;
            return this;
        }

        public RequestBuilder error(int resourceId) {
            this.error = resourceId;
            return this;
        }

        public RequestBuilder override(int width, int height) {
            this.width = width;
            this.height = height;
            return this;
        }

        public void into(ImageView target) {
            this.targetView = target;
            if (placeHolder != 0) {
                target.setImageResource(placeHolder);
            }
            executeLoad(this);
        }
    }

    @SuppressWarnings("all")
    private static synchronized void executeLoad(RequestBuilder builder) {
        if (builder.targetView == null || builder.loadUrl == null) {
            return;
        }
        Disposable cacheDispose = sCacheDispose.get("@" + builder.targetView.hashCode());
        if (cacheDispose != null) {
            cacheDispose.dispose();
        }
        Bitmap cache = sCacheMap.get(builder.loadUrl.toString());
        if (cache != null && !cache.isRecycled()) {
            builder.targetView.setImageBitmap(cache);
            return;
        }
        Drawable gifCache = sGifCacheMap.get(builder.loadUrl.toString());
        if (gifCache != null) {
            builder.targetView.setImageDrawable(gifCache);
            return;
        }

        builder.disposable = Observable.fromCallable(() -> {
                    File file = (File) builder.loadUrl;
                    int[] destWh = getWidthHeight(file.getAbsolutePath());
                    if (builder.asBitmap || destWh[0] * destWh[1] > 2560 * 1440) {
                        Bitmap bitmap = decodeFile(file.getAbsolutePath(), 600 * 600);
                        sCacheMap.put(builder.loadUrl.toString(), bitmap);
                        return bitmap;
                    }
                    String name = file.getName().toLowerCase();
                    if (name.endsWith(".gif") || name.endsWith("webp") || name.endsWith(".svg")) {
                        int len;
                        byte[] buff = new byte[8192];
                        FileInputStream fis = new FileInputStream(file);
                        ByteArrayOutputStream bos = new ByteArrayOutputStream();
                        while ((len = fis.read(buff)) != -1) {
                            bos.write(buff, 0, len);
                        }
                        fis.close();
                        ImageDecoder.Source source = ImageDecoder.createSource(ByteBuffer.wrap(bos.toByteArray()));
                        Drawable drawable = ImageDecoder.decodeDrawable(source);
                        if (drawable instanceof Animatable) {
                            ((Animatable) drawable).start();
                        }
                        sGifCacheMap.put(builder.loadUrl.toString(), drawable);
                        return drawable;
                    }
                    Bitmap bitmap = decodeFile(file.getAbsolutePath(), 600 * 600);
                    sCacheMap.put(builder.loadUrl.toString(), bitmap);
                    return bitmap;
                })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(obj -> {
                    if (obj instanceof Drawable) {
                        builder.targetView.setImageDrawable((Drawable) obj);
                    } else {
                        builder.targetView.setImageBitmap((Bitmap) obj);
                    }
                }, error-> {
                    if (builder.error != 0) {
                        builder.targetView.setImageResource(builder.error);
                    }
                });
        sCacheDispose.put("@" + builder.targetView.hashCode(), builder.disposable);
    }

    public static void dispose() {
        Collection<Disposable> list = sCacheDispose.values();
        for (Disposable d : list) {
            d.dispose();
        }
        sCacheDispose.clear();
    }
}
